package cn.chiship.framework.docs.biz.service.impl;

import cn.chiship.framework.common.constants.CommonCacheConstants;
import cn.chiship.framework.docs.biz.entity.FileResources;
import cn.chiship.framework.docs.biz.pojo.dto.ChunkMultipartFileParam;
import cn.chiship.framework.docs.biz.service.FileResourcesService;
import cn.chiship.framework.docs.biz.service.FileService;
import cn.chiship.framework.docs.core.common.DocsCommonConstants;
import cn.chiship.framework.docs.core.properties.FileUploadProperties;
import cn.chiship.sdk.cache.service.RedisService;
import cn.chiship.sdk.cache.vo.CacheUserVO;
import cn.chiship.sdk.core.base.BaseResult;
import cn.chiship.sdk.core.exception.custom.BusinessException;
import cn.chiship.sdk.core.util.RandomUtil;
import cn.chiship.sdk.framework.pojo.vo.UploadVo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.*;

/**
 * @author lijian
 */
@Service
public class FileServiceImpl implements FileService {

	private static final Logger LOGGER = LoggerFactory.getLogger(FileServiceImpl.class);

	public static final String CHUNK_FOLDER = "/CHUNK_FOLDER_TEMP";

	@Resource
	RedisService redisService;

	@Resource
	FileUploadProperties fileUploadProperties;

	@Resource
	FileResourcesService fileResourcesService;

	@Override
	public BaseResult chunkUploadPost(ChunkMultipartFileParam chunk, HttpServletResponse response) {
		MultipartFile file = chunk.getFile();
		Integer chunkNumber = chunk.getChunkNumber();
		String identifier = chunk.getIdentifier();
		byte[] bytes;
		try {
			bytes = file.getBytes();
			// 这里的不同之处在于这里进行了一个保存分块时将文件名的按照-chunkNumber的进行保存
			Path path = Paths.get(generatePath(fileUploadProperties.getFileAbsolutePath(CHUNK_FOLDER), chunk));
			Files.write(path, bytes);
		}
		catch (IOException e) {
			e.printStackTrace();
		}
		// 这里进行的是保存到redis，并返回集合的大小的操作
		Integer chunks = saveChunk(chunkNumber, identifier);
		// 如果集合的大小和totalChunks相等，判定分块已经上传完毕，进行merge操作
		if (chunks.equals(chunk.getTotalChunks())) {
			return BaseResult.ok(true);
		}
		return BaseResult.ok();
	}

	private static String generatePath(String uploadFolder, ChunkMultipartFileParam chunk) {
		StringBuilder sb = new StringBuilder();
		// 拼接上传的路径
		sb.append(uploadFolder).append(File.separator).append(chunk.getIdentifier());
		// 判断uploadFolder/identifier 路径是否存在，不存在则创建
		if (!Files.isWritable(Paths.get(sb.toString()))) {
			try {
				Files.createDirectories(Paths.get(sb.toString()));
			}
			catch (IOException e) {
				LOGGER.error(e.getMessage(), e);
			}
		}
		// 返回以 - 隔离的分块文件，后面跟的chunkNumber方便后面进行排序进行merge
		return sb.append(File.separator).append(chunk.getFilename()).append("-").append(chunk.getChunkNumber())
				.toString();
	}

	/**
	 * 保存信息到Redis
	 */
	public Integer saveChunk(Integer chunkNumber, String identifier) {
		String key = CommonCacheConstants.buildKey(CommonCacheConstants.REDIS_CHUNK_IDENTIFIER) + ":" + identifier;
		// 获取目前的chunkList
		Set<Integer> oldChunkNumber = (Set<Integer>) redisService.hget(key, "chunkNumberList");
		// 如果获取为空，则新建Set集合，并将当前分块的chunkNumber加入后存到Redis
		if (Objects.isNull(oldChunkNumber)) {
			Set<Integer> newChunkNumber = new HashSet<>();
			newChunkNumber.add(chunkNumber);
			redisService.hset(key, "chunkNumberList", newChunkNumber);
			// 返回集合的大小
			return newChunkNumber.size();
		}
		else {
			// 如果不为空，将当前分块的chunkNumber加到当前的chunkList中，并存入Redis
			oldChunkNumber.add(chunkNumber);
			redisService.hset(key, "chunkNumberList", oldChunkNumber);
			// 返回集合的大小
			return oldChunkNumber.size();
		}

	}

	@Override
	public BaseResult chunkUploadGet(ChunkMultipartFileParam chunk) {
		String identifier = chunk.getIdentifier();
		String key = CommonCacheConstants.buildKey(CommonCacheConstants.REDIS_CHUNK_IDENTIFIER) + ":" + identifier;
		if (redisService.hasKey(key)) {
			Set<Integer> chunkNumbers = (Set<Integer>) redisService.hget(key, "chunkNumberList");
			return BaseResult.ok(chunkNumbers);
		}
		return BaseResult.ok();
	}

	@Override
	public BaseResult mergeFile(ChunkMultipartFileParam chunk, CacheUserVO userVO) {
		String originalName = chunk.getFilename();

		String fileExt = originalName.substring(originalName.lastIndexOf(".") + 1);
		String fileName = RandomUtil.uuidLowerCase() + "." + fileExt;
		originalName = originalName.substring(0, originalName.lastIndexOf("."));
		try {
			String mergeFolder = fileUploadProperties
					.getFileAbsolutePath(fileUploadProperties.getFilePathByCatalogId(chunk.getCatalogId()));

			String filePath = fileUploadProperties
					.getFileUrl(fileUploadProperties.getFilePathByCatalogId(chunk.getCatalogId())) + "/" + fileName;
			// 如果合并后的路径不存在，则新建
			if (!Files.isWritable(Paths.get(mergeFolder))) {
				Files.createDirectories(Paths.get(mergeFolder));
			}
			// 合并的文件名
			String target = mergeFolder + File.separator + fileName;
			// 创建文件
			Files.createFile(Paths.get(target));
			// 遍历分块的文件夹，并进行过滤和排序后以追加的方式写入到合并后的文件
			Files.list(Paths.get(
					fileUploadProperties.getFileAbsolutePath(CHUNK_FOLDER) + File.separator + chunk.getIdentifier()))
					// 过滤带有"-"的文件
					.filter(path -> path.getFileName().toString().contains("-"))
					// 按照从小到大进行排序
					.sorted((o1, o2) -> {
						String p1 = o1.getFileName().toString();
						String p2 = o2.getFileName().toString();
						int i1 = p1.lastIndexOf("-");
						int i2 = p2.lastIndexOf("-");
						return Integer.valueOf(p2.substring(i2)).compareTo(Integer.valueOf(p1.substring(i1)));
					}).forEach(path -> {
						try {
							// 以追加的形式写入文件
							Files.write(Paths.get(target), Files.readAllBytes(path), StandardOpenOption.APPEND);
							// 合并后删除该块
							Files.delete(path);
						}
						catch (IOException e) {
							e.printStackTrace();
						}
					});
			BaseResult baseResult = fileResourcesService.fileResourcesAdd(chunk.getCatalogId(), fileName, originalName,
					chunk.getTotalSize(), chunk.getFileType(), fileExt, userVO.getId(), userVO.getUsername(),
					userVO.getRealName(), DocsCommonConstants.STORAGE_LOCATION_LOCAL, null, filePath);
			if (!baseResult.isSuccess()) {
				return baseResult;
			}
			String identifier = chunk.getIdentifier();
			String key = CommonCacheConstants.buildKey(CommonCacheConstants.REDIS_CHUNK_IDENTIFIER) + ":" + identifier;
			redisService.del(key);
			FileResources fileResources = (FileResources) baseResult.getData();
			UploadVo uploadVo = new UploadVo(fileResources.getId(), fileResources.getOriginalFileName(),
					fileResources.getFileUuid(), fileResources.getFileSize(), fileResources.getFileType(),
					fileResources.getFileExt(), fileResources.getStorageRelativePath(), fileResources.getRealName(),
					fileResources.getGmtCreated());
			return BaseResult.ok(uploadVo);
		}
		catch (IOException e) {
			throw new BusinessException("文件合并出现错误:" + e.getLocalizedMessage());
		}
	}

}
